/*
 * The SEI Software Open Source License, Version 1.0
 *
 * Copyright (c) 2004, Solution Engineering, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Solution Engineering, Inc. (http://www.seisw.com/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 3. The name "Solution Engineering" must not be used to endorse or
 *    promote products derived from this software without prior
 *    written permission. For written permission, please contact
 *    admin@seisw.com.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL SOLUTION ENGINEERING, INC. OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 */

package geom.demo;

import geom.Poly;
import geom.PolyDefault;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import javax.swing.JComponent;
import javax.swing.JOptionPane;

public class PolyCanvas extends JComponent {

	private static final long serialVersionUID = -5842414520065191985L;

	private static final int HALF_POINT_WIDTH = 3;

	private Poly m_Poly1 = new PolyDefault();
	private Vector<PolyDefault> m_Polies = new Vector<PolyDefault>();
	private Vector<PolyDefault> m_PoliesIntersects = new Vector<PolyDefault>();
	
	private NewPointListener pl = new NewPointListener(null, null);
	private List<Point2D> m_PointsPoly1 = new ArrayList<Point2D>();
	private int maxCopies = 2; //can't be 0
    private int activeP_index = 0; //must be 0< && <maxCopies; 
	private List<StateChangedListener> m_Listeners = new ArrayList<StateChangedListener>();
	private int grid_size = 0;
	private State m_StateCurrent;
	private State m_StateEnteringPoly1 = new StatePoly(
			CanvasState.ENTERING_POLY_1, Color.RED, m_Poly1, m_PointsPoly1);
	private State m_StateStopEntering = new StateOperation(
			CanvasState.DONE_ENTERING);
	private State m_StateIntersection = new StateOperationInter(
			CanvasState.INTERSECTION);
	private State m_StateAddVertex = new StateOperation(CanvasState.ADDVERTEX);
	private State m_StateMoveVertex = new StateOperation(CanvasState.MOVEVERTEX);

	/** Creates a new instance of PolyCanvas */
	public PolyCanvas() {
		for (int i = 0; i < maxCopies; ++i) 
			m_Polies.add(new PolyDefault());
		setBackground(Color.white);
		setPreferredSize(new Dimension(500, 500));
		//add switcher for polies:
		clear();
	}

	private void drawGrid(Graphics g) {
		if (grid_size < 2)
			return;
		g.setColor(Color.gray);
		for (int x = 0; x < getWidth() - 1; x += grid_size)
			g.drawLine(x, 0, x, getHeight() - 1);
		for (int y = 0; y < getHeight() - 1; y += grid_size)
			g.drawLine(0, y, getWidth() - 1, y);
		g.setColor(Color.white);
	}

	public void setGrid(int newValue) {
		grid_size = newValue;
		repaint();
	}

	/**
	 * Draw the points and polygons. Also display what to do message when
	 * nothing else displayed and we're in a state to add points.
	 */
	public void paint(Graphics g) {
		Graphics2D g2 = (Graphics2D) g;
		g2.setColor(Color.white);
		g2.fillRect(0, 0, getWidth() - 1, getHeight() - 1);

		drawGrid(g);
		drawPointList(g2, Color.RED, m_PointsPoly1);
		Color arr[] = {Color.ORANGE, Color.GREEN };
		int j=0;
		for (Poly p : m_PoliesIntersects)
			drawPoly(g2, true,arr[j++], p);
		
		drawPoly(g2, false, Color.RED, m_Poly1);
		for (int i = 0; i < maxCopies; ++i)
			drawPoly(g2, false, Color.BLUE, m_Polies.elementAt(i));
	}

	private void drawPointList(Graphics2D g2, Color clr, List<Point2D> points) {
		g2.setColor(clr);
		Point2D prev = null;
		for (Iterator<Point2D> it = points.iterator(); it.hasNext();) {
			Point2D next = (Point2D) it.next();
			drawPoint(g2, clr, next);
			drawLine(g2, clr, prev, next);
			prev = next;
		}
	}

	private void drawPoint(Graphics2D g2, Color clr, Point2D p) {
		int x = (int) p.getX() - HALF_POINT_WIDTH;
		int y = (int) p.getY() - HALF_POINT_WIDTH;
		int width = 2 * HALF_POINT_WIDTH;
		int height = 2 * HALF_POINT_WIDTH;
		g2.setColor(clr);
		g2.fillOval(x, y, width, height);
	}

	private void drawLine(Graphics2D g2, Color clr, Point2D prev, Point2D next) {
		if ((prev == null) || (next == null))
			return;

		g2.setColor(clr);
		int x1 = (int) prev.getX();
		int y1 = (int) prev.getY();
		int x2 = (int) next.getX();
		int y2 = (int) next.getY();
		g2.drawLine(x1, y1, x2, y2);
	}

	private void drawPoly(Graphics2D g2, boolean fill, Color clr, Poly poly) {
		for (int i = 0; i < poly.getNumInnerPoly(); i++) {
			Poly ip = poly.getInnerPoly(i);
			drawInnerPoly(g2, fill, clr, ip);
		}
	}

	private void drawInnerPoly(Graphics2D g2, boolean fill, Color clr, Poly ip) {
		if (ip.isHole())
			g2.setColor(Color.white);
		else
			g2.setColor(clr);

		Polygon jp = new Polygon();
		for (int i = 0; i < ip.getNumPoints(); i++)
			jp.addPoint((int) ip.getX(i), (int) ip.getY(i));

		if (fill)
			g2.fill(jp);
		else
			g2.draw(jp);
	}

	/**
	 * Return true if the set operations can be performed.
	 */
	public boolean canPerformOperations() {
		return !((m_StateCurrent == m_StateEnteringPoly1));
	}

	/**
	 * Return true if data points can be added.
	 */
	public boolean isEnteringData() {
		return (m_StateCurrent == m_StateEnteringPoly1);
	}

	/**
	 * Return true if the current state is intersection.
	 */
	public boolean isIntersectionState() {
		return (m_StateCurrent == m_StateIntersection);
	}

	/**
	 * Return true if the current state is addVertex.
	 */
	public boolean isAddVertexState() {
		return (m_StateCurrent == m_StateAddVertex);
	}

	/**
	 * Return true if the current state is addVertex.
	 */
	public boolean isMoveVertexState() {
		return (m_StateCurrent == m_StateMoveVertex);
	}

	/**
	 * Return the area of the polygon number 1. It returns -1 if data is
	 * currently being accepted for the polygon.
	 */
	public double getPolyArea1() {
		double area = -1.0;
		if (m_StateCurrent != m_StateEnteringPoly1)
			area = m_Poly1.getArea();
		return area;
	}

	/**
	 * Return the area of the resulting polygon of the selected set operation.
	 * It returns -1 if the canvas is not in a set operation state.
	 */
	public double getPolyAreaOp() {
		double area = -1.0;
//		if (m_StateCurrent == m_StateIntersection)
	//		area = m_PolyOp.getArea();
		return area;
	}

	/**
	 * Change the state of the canvas to entering points for Polygon 1.
	 */
	public void startEnteringPoly1() {
		changeState(m_StateEnteringPoly1);
	}

	/**
	 * Change the state of the canvas to adding points for Polygon 1.
	 */
	public void startAdding() {
		if (m_Poly1.getNumPoints() < 2)
			return;
		if (m_StateCurrent != m_StateAddVertex) {
			//this.addMouseListener(pl); // PolyCanvas
			changeState(m_StateAddVertex);
		} else { //stop_adding!!!
			changeState(m_StateIntersection);
		}
	}

	/**
	 * Change the state of the canvas to moving points for Polygon 1.
	 */
	public void startMoving() {
		if (m_Poly1.getNumPoints() < 2)
			return;
		if (m_StateCurrent == m_StateMoveVertex) { //stop_moving!!!
			changeState(m_StateIntersection);
			return;
		}

		changeState(m_StateMoveVertex);
	}

	public void makeCopies() {
		for (int i = 0; i < this.maxCopies; ++i)
			m_Polies.elementAt(i).copy(m_Poly1);
	}

	/**
	 * Change the state of the canvas to stop/done.
	 */
	public void stopEntering() {
		changeState(m_StateStopEntering);
		if (2 < m_Poly1.getNumPoints()) {
			//makeCopies();

			if (m_Poly1.isSelfIntersects())
				JOptionPane.showMessageDialog(null,
				"Self intersecting polygon!");
			intersection();
		} else {
			JOptionPane.showMessageDialog(null,
			"Illegal polygon - not enough vertices");
			clear();
		}

	}

	/**
	 * Change the state of the canvas to intersection.
	 */
	public void intersection() {
		changeState(m_StateIntersection);
		//this.addMouseListener(new NewPointListener(null,null));
		this.addMouseListener(pl);
	}

	/**
	 * Clear the points and polygons of the canvas and reset the state to
	 * entering points for polygon 1.
	 */
	public void clear() {
		if (m_StateCurrent != null)
			m_StateCurrent.stop();
		m_Poly1.clear();
		for (int i = 0; i < maxCopies; ++i)
			m_Polies.elementAt(i).clear();

		for (Poly p : m_PoliesIntersects)
			p.clear();
		m_StateCurrent = m_StateEnteringPoly1;
		m_StateCurrent.start();
		fireStateChanged(m_StateCurrent.getCanvasState());
	}

	/**
	 * Add a listener for changes in state of the canvas.
	 */
	public void addStateChangedListener(StateChangedListener listener) {
		m_Listeners.add(listener);
	}

	// -----------------------
	// --- Private Methods ---
	// -----------------------
	private void changeState(State newState) {
		m_StateCurrent.stop();
		m_StateCurrent = newState;
		m_StateCurrent.start();
		fireStateChanged(m_StateCurrent.getCanvasState());
	}

	private void fireStateChanged(CanvasState newState) {
		for (Iterator<StateChangedListener> it = m_Listeners.iterator(); it
		.hasNext();) {
			StateChangedListener listener = (StateChangedListener) it.next();
			listener.stateChanged(newState);
		}
		firePointsChanged();
	}

	private void firePointsChanged() {
		repaint();
	}

	// ---------------------
	// --- Inner Classes ---
	// ---------------------
	
	private abstract class State {
		private CanvasState m_CanvasState;

		public State(CanvasState state) {
			m_CanvasState = state;
		}

		public String toString() {
			return m_CanvasState.toString();
		}

		public CanvasState getCanvasState() {
			return m_CanvasState;
		}

		public abstract void start();

		public abstract void stop();
	}

	private class StatePoly extends State {
		private Poly m_Poly;
		private List<Point2D> m_Points;
		private NewPointListener m_NewPointListener;

		public StatePoly(CanvasState state, Color clr, Poly poly,
				List<Point2D> points) {
			super(state);
			m_Poly = poly;
			m_Points = points;
			m_NewPointListener = new NewPointListener(clr, m_Points);
		}

		public void start() {
			m_Points.clear();
			m_Poly.clear();
			for (Poly p : m_PoliesIntersects)
				p.clear();

			for (MouseListener ml : PolyCanvas.this.getMouseListeners())
				PolyCanvas.this.removeMouseListener(ml);
			for (MouseMotionListener ml : PolyCanvas.this
					.getMouseMotionListeners())
				PolyCanvas.this.removeMouseMotionListener(ml);
				
			PolyCanvas.this.addMouseListener(m_NewPointListener);
			PolyCanvas.this.addMouseMotionListener(m_NewPointListener);
			PolyCanvas.this.setCursor(Cursor
					.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
		}

		public void stop() {
			for (Iterator<Point2D> it = m_Points.iterator(); it.hasNext();) {
				Point2D p = (Point2D) it.next();
				m_Poly.add(p);
			}
			m_Points.clear();
			PolyCanvas.this.removeMouseListener(m_NewPointListener);
			// PolyCanvas.this.removeMouseMotionListener( m_NewPointListener );
			PolyCanvas.this.setCursor(Cursor.getDefaultCursor());
		}
	}

	private class StateOperation extends State {
		public StateOperation(CanvasState state) {
			super(state);
		}

		public void start() {
			PolyCanvas.this.addMouseMotionListener(pl);
			
		}

		public void stop() {
			for (Poly p : m_PoliesIntersects)
				p.clear();
		}
	}

	private class StateOperationInter extends StateOperation {
		public StateOperationInter(CanvasState state) {
			super(state);
		}

		public void start() {
			super.start();
			makeCopies();
			m_PoliesIntersects.clear();
			for (Poly p : m_Polies)
				m_PoliesIntersects.add((PolyDefault) m_Poly1.intersection(p));
	
	  }
	}
	/**
	 * React mouse actions on the canvas.
	 */
	private class NewPointListener implements MouseListener,
	MouseMotionListener {
		private boolean m_IsDragging = false;
		private int indexDragged = -1;
		private List<Point2D> m_Points;
		private Color m_Color;
		private Point2D m_DragPoint = new Point2D.Double(-10.0, -10.0);

		public NewPointListener(Color clr, List<Point2D> points) {
			m_Color = clr;
			m_Points = points;
		}

		public void mouseClicked(MouseEvent e) {
		}

		public void mouseEntered(MouseEvent e) {
		}

		public void mouseExited(MouseEvent e) {
			// mouseReleased(e);
		}

		public void mousePressed(MouseEvent e) {
			if (e.getButton() == MouseEvent.BUTTON1) {
				m_IsDragging = true;
			}
		}

		public void mouseReleased(MouseEvent e) {
			if (3==e.getButton()){
				activeP_index = (activeP_index + 1)%maxCopies;
				return;
			}
			//======================================
			if (isAddVertexState()) {
				makeCopies();
				int ind0 = (m_Polies.elementAt(0).getVertIndClosestToPoint(e
						.getX(), e.getY(), 0));
				m_Polies.elementAt(0).insert(e.getX(), e.getY(), ind0);
				if (m_Polies.elementAt(0).isSelfIntersects()) {
					m_Polies.elementAt(0).clear();
					return;
				} else {
					m_Poly1.clear();
					m_Poly1.copy(m_Polies.elementAt(0));
				}

			}
			//======================================
			else if (m_IsDragging && isEnteringData()) {
				Point2D p = new Point2D.Double((double) e.getX(), (double) e
						.getY());

				m_Points.add(p);
				m_DragPoint.setLocation(-10, -10);
				firePointsChanged();
			}
			//======================================
			if (m_IsDragging && isMoveVertexState()) {
				if (indexDragged < 0)
					return;
				m_Poly1.setXY(indexDragged, e.getX(), e.getY());
				fireStateChanged(m_StateCurrent.getCanvasState());
				if (m_Poly1.isSelfIntersects())
					JOptionPane.showMessageDialog(null,
					"Self intersecting polygon!");
			}
			m_IsDragging = false;
			indexDragged = -1;
		}

		public void mouseDragged(MouseEvent e) {

			if (!m_IsDragging)
				return;
			//======================================
			if (isEnteringData()) {
				Point2D last_point = null;
				if (m_Points.size() > 0) {
					last_point = (Point2D) m_Points.get(m_Points.size() - 1);
				}
				Graphics2D g2 = (Graphics2D) PolyCanvas.this.getGraphics();
				g2.setXORMode(Color.white);

				// Erase Previous point and line
				if (m_DragPoint.getX() > 0.0) {
					PolyCanvas.this.drawLine(g2, m_Color, last_point,
							m_DragPoint);
					PolyCanvas.this.drawPoint(g2, m_Color, m_DragPoint);
				}

				// Draw next point
				m_DragPoint.setLocation((double) e.getX(), (double) e.getY());
				PolyCanvas.this.drawPoint(g2, m_Color, m_DragPoint);
				PolyCanvas.this.drawLine(g2, m_Color, last_point, m_DragPoint);

			}
			//======================================
			if (isMoveVertexState()) {
					makeCopies();

				if (indexDragged < 0)
					indexDragged = (m_Polies.elementAt(0)
							.getVertIndClosestToPoint(e.getX(), e.getY(), 0));
				for (int i = 0; i < maxCopies; ++i)
					m_Polies.elementAt(i).setXY(indexDragged, e.getX(),
							e.getY());

				fireStateChanged(m_StateCurrent.getCanvasState());
			}
		}

		public void mouseMoved(MouseEvent e) {
//=============================================================			
			if (isAddVertexState()) {
				makeCopies();
				int ind0 = (m_Poly1.getVertIndClosestToPoint(e.getX(),
						e.getY(), 0));
				m_Polies.elementAt(0).insert(e.getX(), e.getY(), ind0);
				if (m_Polies.elementAt(0).isSelfIntersects()) {
					m_Polies.elementAt(0).clear();
					return;
				}
			}
//=============================================================
			if (isMoveVertexState()) {
				int ind0 = (m_Poly1.getVertIndClosestToPoint(e.getX(),
						e.getY(), 0));
				m_Polies.elementAt(0).clear();
				m_Polies.elementAt(0).add(m_Poly1.getX(ind0),
						m_Poly1.getY(ind0));
				m_Polies.elementAt(0).add(m_Poly1.getX(ind0) - 10,
						m_Poly1.getY(ind0) + 10);
				m_Polies.elementAt(0).add(m_Poly1.getX(ind0) - 10,
						m_Poly1.getY(ind0) + 5);

			}
//=============================================================
			if (isIntersectionState()) {

			//	int rel_x = (int) -m_Poly1.getX(0);
				//int rel_y = (int) -m_Poly1.getY(0);

				if (m_Polies.size()==0)
					makeCopies();
				
				int rel_x0 = (int) -m_Polies.elementAt(activeP_index).getX(0);
				int rel_y0 = (int) -m_Polies.elementAt(activeP_index).getY(0);
				
				m_Polies.elementAt(activeP_index).move_by(rel_x0 +e.getX(),	rel_y0 + e.getY());
			//	m_Polies.elementAt(1).move_by(rel_x + e.getX(),	rel_y + e.getY());

				m_PoliesIntersects.clear();
				for (Poly p : m_Polies)
					m_PoliesIntersects.add((PolyDefault) m_Poly1.intersection(p));
			}
			fireStateChanged(m_StateCurrent.getCanvasState());

		}
	}
}
